]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
timeLimitsManager: Don't adjust times for offset changes after resume
authorSebastian Keller <skeller@gnome.org>
Wed, 30 Apr 2025 11:13:41 +0000 (13:13 +0200)
committerMarge Bot <marge-bot@gnome.org>
Mon, 5 May 2025 17:47:49 +0000 (17:47 +0000)
The non-continuous change in wall clock time after a suspend/resume
triggers timeChangeNotify which adjusts the stored time data according
to the difference in clock offsets. The monotonic clock however might
not have advanced during the suspend. Since the offsets are the
difference between the wall clock time and monotonic time, the
difference between the old and new offset will be the duration of the
sleep if there was no change in the monotonic clock. Adjusting the time
data by this will move the beginning of the inactive period from the
suspend forward in time to just before the resume, effectively removing
it. Since all previous times are adjusted as well, this will result in
time data as if the system had been running continuously, but was
started later than it actually was.

Fix this by not adjusting times on resume and just updating the current
offset to account for the monotonic clock not advancing during sleep.

Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/8289
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3707>

js/misc/timeLimitsManager.js
tests/unit/timeLimitsManager.js

index 29417539836aac0e6432d11e1b0f407da7e98661..dc4590584d1b1fb4a729f156f2dd41520f086bd4 100644 (file)
@@ -175,6 +175,7 @@ export const TimeLimitsManager = GObject.registerClass({
         this._timerId = 0;
         this._timeChangeId = 0;
         this._clockOffsetSecs = 0;
+        this._ignoreClockOffsetChanges = false;
 
         // Start tracking timings
         this._updateSettings();
@@ -231,6 +232,9 @@ export const TimeLimitsManager = GObject.registerClass({
         this._clockOffsetSecs = this._getClockOffset();
         try {
             this._timeChangeId = this._clock.timeChangeNotify(() => {
+                if (this._ignoreClockOffsetChanges)
+                    return GLib.SOURCE_CONTINUE;
+
                 const newClockOffsetSecs = this._getClockOffset();
                 const oldClockOffsetSecs = this._clockOffsetSecs;
 
@@ -348,9 +352,14 @@ export const TimeLimitsManager = GObject.registerClass({
     }
 
     async _onPrepareForSleep(preparingForSleep) {
-        // Just come back from sleep, so take another inhibitor.
-        if (!preparingForSleep)
+        // Just come back from sleep, so take another inhibitor. Also update
+        // the clock offset to account for the monotonic clock not advancing
+        // during sleep.
+        if (!preparingForSleep) {
             this._ensureInhibitor();
+            this._clockOffsetSecs = this._getClockOffset();
+            this._ignoreClockOffsetChanges = false;
+        }
 
         try {
             await this._updateUserState(true);
@@ -358,9 +367,14 @@ export const TimeLimitsManager = GObject.registerClass({
             console.warn(`Failed to update user state: ${e.message}`);
         }
 
-        // Release the inhibitor if we’re preparing to sleep.
-        if (preparingForSleep)
+        // Release the inhibitor if we’re preparing to sleep. Also avoid
+        // adjusting the time due to offset changes caused by the monotonic
+        // clock not advancing during sleep until we have updated the current
+        // offset to account for this.
+        if (preparingForSleep) {
+            this._ignoreClockOffsetChanges = true;
             this._releaseInhibitor();
+        }
     }
 
     /** Shut down the state machine and write out the state file. */
index 3eba0aa7c6de195aa5983e4a629bda9b0ab1b59d..bcbd77e885011dfbcdbefac6411ac1591d9fb542 100644 (file)
@@ -282,6 +282,12 @@ class TestHarness {
             time: TestHarness.timeStrToSecs(timeStr),
             newPreparingForSleepState: true,
         });
+        this._insertEvent({
+            type: 'time-change',
+            time: TestHarness.timeStrToSecs(timeStr) + 1,
+            newTime: TestHarness.timeStrToSecs(timeStr) + duration - 1,
+            callback: null,
+        });
         this._insertEvent({
             type: 'preparing-for-sleep-state-change',
             time: TestHarness.timeStrToSecs(timeStr) + duration,
@@ -1309,6 +1315,19 @@ describe('Time limits manager', () => {
             'dailyLimitTime': TestHarness.timeStrToSecs('2024-06-01T17:00:00Z'),
         });
 
+        // Ensure the suspend event caused a state change to inactive and its
+        // time was not adjusted due to offset changes on resume
+        harness.addAssertionEvent('2024-06-01T15:00:02Z', () => {
+            const [, historyContents] = harness.mockHistoryFile.load_contents(null);
+            expect(JSON.parse(new TextDecoder().decode(historyContents)))
+                .withContext('History file contents')
+                .toContain({
+                    'oldState': UserState.ACTIVE,
+                    'newState': UserState.INACTIVE,
+                    'wallTimeSecs': TestHarness.timeStrToSecs('2024-06-01T12:00:01Z'),
+                });
+        });
+
         harness.shutdownManager('2024-06-01T15:20:00Z', timeLimitsManager);
 
         harness.run();